package com.maikobi.maikobi.aop;

import com.maikobi.maikobi.annotation.RateLimiter;
import com.maikobi.maikobi.common.ErrorCode;
import com.maikobi.maikobi.exception.BusinessException;
import com.maikobi.maikobi.manager.RedisLimiterManager;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * RateLimiterAspect类用于实现基于Redis的分布式限流功能
 * 它利用AOP(面向切面编程)在方法执行前进行拦截和限流判断
 * @author: Maiko7
 * @create: 2025-07-14 8:20
 */
@Aspect
@Component
@Slf4j
public class RateLimiterAspect {

    @Resource
    private RedisLimiterManager redisLimiterManager;

    /**
     * 环绕通知，拦截所有被 @RateLimiter 注解的方法，进行限流处理
     *
     * @annotation(rateLimiter)：这是切点表达式，表示拦截所有被 @RateLimiter 注解标注的方法。
     * 这里的 rateLimiter 是一个参数名，代表拦截到的注解实例。
     *
     * 也就是说，Spring 会把被拦截方法上 @RateLimiter 注解的实例传给这个参数，方便我们在通知方法里使用注解里的属性，比如限流次数、时间单位、key 等。
     *
     * @param joinPoint    被代理的方法调用点
     * @param rateLimiter  注解中配置的限流参数
     * @return 原方法的执行结果
     * @throws Throwable   允许抛出任何异常
     */
    @Around("@annotation(rateLimiter)")
    public Object around(ProceedingJoinPoint joinPoint, RateLimiter rateLimiter) throws Throwable {
        // 获取当前请求对象
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

        // 动态解析注解中配置的 key（如 "user_register:#{ip}" 或 "user_search:#{userId}"）
        String keyTemplate = rateLimiter.key();
        String realKey = parseKey(keyTemplate, request);

        // 使用 Redisson 令牌桶算法进行限流（你的代码中切换到了 Redisson 限流，所以这里也跟着改动）
        redisLimiterManager.doRateLimitByRedisson(realKey, rateLimiter.permits(), rateLimiter.time(), rateLimiter.unit());

        // 继续执行目标方法
        return joinPoint.proceed();
    }

    /**
     * 根据模板动态生成限流 key，如支持 #{ip} 或 #{userId}
     *
     * @param template key 模板字符串，例如 "genChartByAi_#{userId}" 或 "user_register_#{ip}"
     * @param request  当前请求对象
     * @return 替换后的实际 key，例如 "genChartByAi_123" 或 "user_register_192.168.1.1"
     */
    private String parseKey(String template, HttpServletRequest request) {
        String realKey = template;

        // 优先判断是否包含 #{ip}
        if (template.contains("#{ip}")) {
            String ip = getClientIp(request);
            if (ip != null && !ip.isEmpty()) {
                realKey = template.replace("#{ip}", ip);
            } else {
                // 如果 IP 为空，可以考虑抛出异常、使用默认值或日志记录
                 throw new BusinessException(ErrorCode.INVALID_REQUEST, "无法获取客户端IP进行限流");
                // 或者 realKey = template.replace("#{ip}", "unknown_ip");
            }
        }
        // 再判断是否包含 #{userId}，注意这里是 else if，避免同时替换
        else if (template.contains("#{userId}")) {
            // 确保在请求的生命周期早期（例如通过拦截器或过滤器）将 "loginUser" 对象设置到 request attributes 中
            Object loginUser = request.getAttribute("loginUser");
            Long userId = getUserId(loginUser);
            realKey = template.replace("#{userId}", String.valueOf(userId));
        } else {
            // 如果既不包含 #{ip} 也不包含 #{userId}，则直接使用模板作为 key
            // 这适用于固定 key 的限流场景，例如 "global_api_limit"
        }
        return realKey;
    }

    /**
     * 获取客户端真实 IP 地址，兼容多级代理场景
     *
     * @param request 当前请求对象
     * @return 客户端 IP 地址
     */
    private String getClientIp(HttpServletRequest request) {
        String[] headers = {
                "X-Forwarded-For",
                "Proxy-Client-IP",
                "WL-Proxy-Client-IP",
                "HTTP_CLIENT_IP",
                "HTTP_X_FORWARDED_FOR"
        };

        for (String header : headers) {
            String ip = request.getHeader(header);
            if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
                // 如果存在多个 IP（例如 X-Forwarded-For 会以逗号分隔），取第一个非 unknown 的 IP
                return ip.split(",")[0].trim();
            }
        }

        String ip = request.getRemoteAddr();
        // 本地访问时的特殊处理
        if ("0:0:0:0:0:0:0:1".equals(ip)) {
            return "127.0.0.1";
        }
        return ip;
    }

    /**
     * 获取当前用户的 userId（通过反射调用 user.getId() 方法）
     *
     * @param user 用户对象
     * @return 用户 ID，如果获取失败则返回 0L
     */
    private Long getUserId(Object user) {
        if (user == null) {
            return 0L; // 如果用户对象为空，返回 0L
        }
        try {
            // 假设用户对象有一个名为 getId() 的方法，并返回 Long 类型
            return (Long) user.getClass().getMethod("getId").invoke(user);
        } catch (Exception e) {
            // 记录异常，方便调试
            // log.error("从用户对象获取 userId 失败", e);
            return 0L; // 获取失败时返回 0L
        }
    }
}

//public class RateLimiterAspect {
//
//    @Resource
//    private RedisLimiterManager redisLimiterManager;
//
//    /**
//     * 环绕通知，拦截所有被 @RateLimiter 注解的方法，进行限流处理
//     *
//     * @param joinPoint   被代理的方法调用点
//     * @param rateLimiter 注解中配置的限流参数
//     * @return 原方法的执行结果
//     * @throws Throwable 允许抛出任何异常
//     * @annotation(rateLimiter)：这是切点表达式，表示拦截所有被 @RateLimiter 注解标注的方法。
//     * 这里的 rateLimiter 是一个参数名，代表拦截到的注解实例。
//     * <p>
//     * 也就是说，Spring 会把被拦截方法上 @RateLimiter 注解的实例传给这个参数，方便我们在通知方法里使用注解里的属性，比如限流次数、时间单位、key 等。
//     */
//    @Around("@annotation(rateLimiter)")
//    public Object around(ProceedingJoinPoint joinPoint, RateLimiter rateLimiter) throws Throwable {
//        // 获取当前请求对象
//        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
//
//        // 动态解析注解中配置的 key（如 "user_register:#{ip}"）
//        String keyTemplate = rateLimiter.key();
//        String realKey = parseKey(keyTemplate, request);
//
//        // 执行限流逻辑（如漏桶/令牌桶）
//        redisLimiterManager.doRateLimitByRedisCounter(realKey, rateLimiter.permits(), rateLimiter.time(), rateLimiter.unit());
//
//        // 继续执行目标方法
//        return joinPoint.proceed();
//    }
//
//
//    /**
//     * 根据模板动态生成限流 key，如支持 #{ip} 或 #{userId}
//     *
//     * @param template key 模板字符串，例如 "genChartByAi_#{userId}"
//     * @param request  当前请求对象
//     * @return 替换后的实际 key，例如 "genChartByAi_123"
//     */
//    private String parseKey(String template, HttpServletRequest request) {
//        String ip = getClientIp(request);
//        if (template.contains("#{ip}") && ip != null && !ip.isEmpty()) {
//            // 如果模板中有 #{ip} 且 ip 不为空，只替换 ip 并返回
//            return template.replace("#{ip}", ip);
//        }
//
//        // 从请求中获取用户对象（可根据实际业务改为从 token、session、securityContext 获取）
//        Object loginUser = request.getAttribute("loginUser");
//        Long userId = getUserId(loginUser);
//        return template.replace("#{userId}", userId.toString());
//        // 替换模板中的变量
////        return template
////                .replace("#{ip}", ip)
////                .replace("#{userId}", userId.toString());
//    }
//
//    //    private String getClientIp(HttpServletRequest request) {
////        String ip = request.getHeader("X-Forwarded-For");
////        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
////            ip = request.getRemoteAddr();
////        } else {
////            ip = ip.split(",")[0];
////        }
////        return ip;
////    }
//
//    /**
//     * 获取客户端真实 IP 地址，兼容多级代理场景
//     *
//     * @param request 当前请求对象
//     * @return 客户端 IP 地址
//     */
//    private String getClientIp(HttpServletRequest request) {
//        String[] headers = {
//                "X-Forwarded-For",
//                "Proxy-Client-IP",
//                "WL-Proxy-Client-IP",
//                "HTTP_CLIENT_IP",
//                "HTTP_X_FORWARDED_FOR"
//        };
//
//        for (String header : headers) {
//            String ip = request.getHeader(header);
//            if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
//                // 如果存在多个 IP，用第一个非 unknown 的
//                return ip.split(",")[0].trim();
//            }
//        }
//
//        String ip = request.getRemoteAddr();
//        // 本地访问时的特殊处理
//        if ("0:0:0:0:0:0:0:1".equals(ip)) {
//            return "127.0.0.1";
//        }
//        return ip;
//    }
//
//    /**
//     * 获取当前用户的 userId（通过反射调用 user.getId() 方法）
//     *
//     * @param user 用户对象
//     * @return 用户 ID，如果获取失败则返回 0
//     */
//    private Long getUserId(Object user) {
//        if (user == null) return 0L;
//        try {
//            return (Long) user.getClass().getMethod("getId").invoke(user);
//        } catch (Exception e) {
//            return 0L;
//        }
//    }
//}


//    // 注入RedissonClient用于操作Redis
//    @Resource
//    private RedissonClient redissonClient;
//
//    /**
//     * 围绕通知方法，用于在执行带有@RateLimiter注解的方法前后添加限流逻辑
//     *
//     * @param joinPoint 切入点，包含被拦截方法的信息
//     * @return 执行结果
//     * @throws Throwable 可能抛出的异常
//     */
//    @Around("@annotation(com.maiko.usercenter.annotation.RateLimiter)")
//    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
//        // 获取方法签名和限流注解信息
//        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
//        Method method = methodSignature.getMethod();
//        RateLimiter rateLimiter = method.getAnnotation(RateLimiter.class);
//
//        // 解析限流注解的参数
//        String key = rateLimiter.key();
////        String keyPrefix = rateLimiter.key();
////        String key = parseDynamicKey(rateLimiter.key(), method, joinPoint.getArgs());
////        String key = parseDynamicKey(rateLimiter.key(), joinPoint.getArgs());
//        long permits = rateLimiter.permits();
//        long time = rateLimiter.time();
//        TimeUnit unit = convertToTimeUnit(rateLimiter.unit());
//
//        // 调用统一工具类生成限流key（只保留一行）
//        // String fullKey = RateLimiterKeyUtil.buildFullKey(keyPrefix);
//
//        // 初始化限流器
////        RRateLimiter rRateLimiter = redissonClient.getRateLimiter(key);
//        RRateLimiter rRateLimiter = redissonClient.getRateLimiter(key);
//        /**
//         * 设置分布式限流规则，表示在指定时间窗口内允许的最大请求数。
//         *
//         * 参数说明：
//         * 1. RateType.OVERALL：表示整体速率限制模式，适用于所有调用该资源的客户端。
//         *    其他可选类型如 PER_CLIENT，则表示每个客户端独立计数。
//         * 2. permits：每单位时间允许的最大请求次数。
//         *    示例：若 permits=5，表示最多允许5次请求通过。
//         * 3. Duration.of(time, unit.toChronoUnit())：定义时间窗口大小。
//         *    示例：time=1, unit=MINUTES → 时间窗口为1分钟。
//         *
//         *
//         * 使用示例：
//         * 假设你有一个接口被多个用户并发访问，你可以使用如下注解进行限流控制：
//         *
//         *     @RateLimiter(key = "user_login", permits = 5, time = 1, unit = TimeUnit.MINUTES)
//         *     public void login(String username) {
//         *         // 用户登录逻辑
//         *     }
//         *
//         * 上述配置表示：每1分钟内，每个客户端最多只能尝试登录5次（根据 key=user_login 判断）。
//         */
//        rRateLimiter.trySetRate(RateType.OVERALL, permits, Duration.of(time, unit.toChronoUnit()));
//
//        // 尝试获取限流令牌
//        boolean canPass = rRateLimiter.tryAcquire(1);
//        if (!canPass) {
//            // 如果获取失败，则记录日志并抛出异常，阻止方法执行
//            log.warn("触发限流：key={}, permits={}, time={}, unit={}", key, permits, time, unit);
//            throw new BusinessException(ErrorCode.TOO_MANY_REQUEST, "请求过于频繁，请稍后再试");
//        }
//        // 如果获取成功，则继续执行原方法
//        return joinPoint.proceed();
//    }
//
//    /**
//     * 将RateLimiter注解中的时间单位转换为TimeUnit对象
//     *
//     * @param timeUnit 注解中指定的时间单位
//     * @return 对应的TimeUnit对象，默认为Seconds
//     */
//    private TimeUnit convertToTimeUnit(TimeUnit timeUnit) {
//        return timeUnit == null ? TimeUnit.SECONDS : timeUnit;
//    }


//    private TimeUnit convertToTimeUnit(TimeUnit timeUnit) {
//        if (timeUnit == null) {
//            return TimeUnit.SECONDS;
//        }
//        switch (timeUnit) {
//            case MILLISECONDS:
//                return TimeUnit.MILLISECONDS;
//            case SECONDS:
//                return TimeUnit.SECONDS;
//            case MINUTES:
//                return TimeUnit.MINUTES;
//            case HOURS:
//                return TimeUnit.HOURS;
//            case DAYS:
//                return TimeUnit.DAYS;
//            default:
//                return TimeUnit.SECONDS;
//        }
//    }
//
//
//}